package com.icbc.dccsh.functions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import net.objecthunter.exp4j.function.Function;

public class FormulaEngine {
	protected String _formula;
	protected String[] _variableNames;
	protected HashMap<String, Object> _variableName_value_mapping;

	protected FormulaEngine() {
		// prevent be constructed outside
	}

	public static boolean IsSimpleFormula(String rawFormula) {
		if (StringUtils.isEmpty(rawFormula)) {
			return true;
		} else {
			boolean isSimple = Pattern.matches("^\\w+$", rawFormula);
			return isSimple;
		}
	}

	public static FormulaEngine GetFormulaEngine(String rawFormula) {
		FormulaEngine e;
		if (rawFormula.contains("_all_")) {
			e = new AggregrationFormulaEngine();
			e.formula(rawFormula);
		} else {
			e = new FormulaEngine();
			e.formula(rawFormula);
		}
		return e;
	}

	private static Function[] _customFunctions;

	static {
		List<Function> functionList = new ArrayList<Function>();
		functionList.add(new AverageFunction());
		functionList.add(new SumFunction());
		_customFunctions = new Function[functionList.size()];
		_customFunctions = functionList.toArray(_customFunctions);
	}

	private FormulaEngine formula(String formula) {
		this._formula = formula;

		return this;
	}

	// e.g. formula = "(x + y) / z", then variablesValueList is
	// {x=1, y=2, z=3}
	public FormulaEngine variables(HashMap<String, Object> variableNameValueMapping) {
		HashMap<String, Object> variableName_value_mapping = new HashMap<String, Object>();
		for (Map.Entry<String, Object> kve : variableNameValueMapping.entrySet()) {
			// if (kve.getKey().endsWith("___prior")) // skip the prior value
			// continue;
			variableName_value_mapping.put(kve.getKey(), kve.getValue());
		}

		List<String> variableNameList = new ArrayList<String>();
		for (Map.Entry<String, Object> entry : variableName_value_mapping.entrySet()) {
			variableNameList.add(entry.getKey());
		}
		this._variableNames = new String[variableNameList.size()];
		this._variableNames = variableNameList.toArray(this._variableNames);
		this._variableName_value_mapping = variableName_value_mapping;
		return this;
	}

	public Object calculate() {
		try {
			// if (StringUtils.isEmpty(this._formula)) {
			// for (Object o : this._variableName_value_mapping.values()) {
			// return o;
			// }
			// }
			Expression e = new ExpressionBuilder(this._formula).functions(this._customFunctions)
					.variables(this._variableNames).build().setVariables(this._variableName_value_mapping);
			double result = e.evaluate();
			return result;
		} catch (RuntimeException ex) {
			System.out.println(String.format("processing formula [%s] throw exceptions...", this._formula));
			for (Map.Entry<String, Object> kvp : this._variableName_value_mapping.entrySet()) {
				System.out.print(
						String.format("watching variables... k: [%s], v: [%s]...", kvp.getKey(), kvp.getValue()));
			}
			throw ex;
		}
	}
}
